
#include <stdlib.h>
#include <stdint.h>
#include "config.h"

#include "zbd-cache.h"
#include "log.h"

#include "algorithms-general.h"

/* zbd-cache.h */
struct cache_page;
extern struct zbd_zone *zones_collection;
extern struct cache_runtime cache_rt;

extern int RMW(uint32_t zoneId, uint64_t from_blk, uint64_t to_blk); 
extern int Page_force_drop(struct cache_page *page);


/* sla_ica3pp-specified objs */
struct page_payload{
    uint64_t stamp;
    struct cache_page *lru_w_pre, *lru_w_next;
    struct cache_page *lru_r_pre, *lru_r_next;
    int status;
};

static uint64_t Stamp_GLOBAL = 0;
static uint64_t window = 0;
static uint64_t Stamp_OOD; // = Stamp_GLOBAL - window;

// CARS对读/写数据分来管理：写数据按zone组织lru，读数据按全局组织lru
struct sla_ica3pp_lru {
    struct cache_page *head, *tail;
};  // 该结构体用于 (struct zbd_zone *) zone 的 priv字段来表示写block的LRU链表；和用于全局读block的LRU链表。


/* lru Utils */
static inline void lru_insert(struct cache_page *page, int op);
static inline void lru_remove(struct cache_page *page, int op);
static inline void lru_move(struct cache_page *page, int op);
static inline void lru_top(struct cache_page *page, int op);

/* cache out */
static int sla_ica3pp_get_zone_out();

int sla_ica3pp_init()
{
    /* init page priv field. */
    struct cache_page *page = cache_rt.pages;
    for (uint64_t i = 0; i < STT.n_cache_pages; page++, i++)
    {
        page->priv = calloc(1, sizeof(struct page_payload));
        if(!page->priv)
            return -1;
    }

    struct zbd_zone *z = zones_collection;
    for(int i = 0; i < N_ZONES; i++, z++){
        z->priv = calloc(1, sizeof(struct sla_ica3pp_lru));
    }

    window = STT.n_cache_pages;

    return 0;
}

int sla_ica3pp_login(struct cache_page *page, int op)
{   
    lru_insert(page, op);

    struct page_payload *payload = (struct page_payload *)page->priv;

    payload->stamp = Stamp_GLOBAL;
    Stamp_GLOBAL ++;
    return 0;
}

int sla_ica3pp_logout(struct cache_page *page, int op)
{   
    lru_remove(page, op);
    
    return 0;
}

int sla_ica3pp_hit(struct cache_page *page, int op)
{
    struct page_payload *payload = (struct page_payload *)page->priv;

    lru_top(page, op);

    if (page->status & (~op)){
        lru_top(page, page->status & (~op)); 
    }
    

    payload->stamp = Stamp_GLOBAL;

    Stamp_GLOBAL ++; 
    return 0;
}

int sla_ica3pp_writeback_privi(int type)
{
    int ret;

EVICT_DIRTY:
    ret = sla_ica3pp_get_zone_out();
    return ret;
}

static int sla_ica3pp_get_zone_out()
{
    int best_zoneId = -1;
    uint32_t from = 0, to = N_ZONEBLK - 1;
    int blks_ars = 0, blks_sla_ica3pp = 0;
    int best_zweight = 0x80000000; // -(2^31)

    // Traverse every zone. 
    struct zbd_zone *z = zones_collection;
    for(int i = 0; i < N_ZONES; i++, z++)
    {   
        int footprint = z->cblks;
        if(!footprint)
            continue;

        int cleanblks = z->cblks - z->cblks_wtr;
        int hits_recent = z->hits; 
        int zweight_score = footprint - hits_recent + cleanblks;

        // if(zweight_score < 0){
        //     int a = 0;
        // }

        if(zweight_score > best_zweight){
            best_zweight = zweight_score;

            best_zoneId = z->zoneId;            
        }
    }

    z = zones_collection + best_zoneId;

    log_info_sac("[%s]: zonrID: %d, weight: %d, footprint: %d, hits: %d, cleanblocks: %d \n", __func__, z->zoneId, best_zweight, z->cblks, z->hits, z->cblks - z->cblks_wtr);

    from = 0;
    // RMW
    RMW(best_zoneId, from, to);
    
    // evict clean blocks
    struct sla_ica3pp_lru * zone_lru = (struct sla_ica3pp_lru *)z->priv;
    struct cache_page *clean_page = zone_lru->tail;
    int cnt = 0;
    while(clean_page){
       Page_force_drop(clean_page);
       clean_page = zone_lru->tail;
       cnt ++;
    }

    return best_zoneId;
}


/* lru Utils */
static inline void lru_insert(struct cache_page *page, int op)
{
    struct zbd_zone *zone = zones_collection + page->belong_zoneId;

    struct sla_ica3pp_lru * zone_lru = (struct sla_ica3pp_lru *)zone->priv;
    struct page_payload *payload = (struct page_payload *)page->priv;

    #ifdef DEBUG_CARS // 用于调试zbd-cache.c代码的正确性
    if(payload->status & op)
    {
        // already in lru link
        log_err_sac("[error] func:%s, page is already in LRU list. \
            You may call algorithm.logout() before algorithm.login() \n");
        exit(-1);
    }
    #endif

    if(op & FOR_WRITE || op & FOR_READ){
        if (zone_lru->head == NULL)
        {
            zone_lru->head = zone_lru->tail = page;
        }
        else
        {
            struct page_payload *header_payload = (struct page_payload *)zone_lru->head->priv;

            payload->lru_w_pre = NULL;
            payload->lru_w_next = zone_lru->head;
            
            header_payload->lru_w_pre = page;
            zone_lru->head = page;
        }  
    } 
    else{
        log_err_sac("[%s] error. \n", __func__);
        exit(EXIT_FAILURE);
    }
    
    payload->status |= op;
}

static inline void lru_remove(struct cache_page *page, int op)
{
    struct page_payload *payload_this = (struct page_payload *)page->priv;
    struct page_payload *payload_pre, *payload_next;


    struct zbd_zone *zone = zones_collection + page->belong_zoneId;
    struct sla_ica3pp_lru * zone_lru = (struct sla_ica3pp_lru *)zone->priv;    

    if(payload_this->lru_w_pre)
    {
            payload_pre = (struct page_payload *)payload_this->lru_w_pre->priv;
            payload_pre->lru_w_next = payload_this->lru_w_next;
    } else 
    {
        zone_lru->head = payload_this->lru_w_next;
    }
    
    if(payload_this->lru_w_next){
            payload_next = (struct page_payload *)payload_this->lru_w_next->priv;
            payload_next->lru_w_pre = payload_this->lru_w_pre;
    } else 
    {
        zone_lru->tail = payload_this->lru_w_pre;
    }
    payload_this->lru_w_pre = payload_this->lru_w_next = NULL;


    payload_this->status = FOR_UNKNOWN;
}

static inline void lru_top(struct cache_page *page, int op)
{
    lru_remove(page, op);
    lru_insert(page, op);
}

